package com.unc.allergyex.helpers;


import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import android.content.Context;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;

import com.google.gson.Gson;
import com.unc.allergyex.AllergyEX;
import com.unc.allergyex.objects.VersionNumber;
import com.unc.allergyex.objects.VersionResult;
import com.unc.allergyex.objects.db.Allergen;
import com.unc.allergyex.objects.db.Restaurant;

/**
 * Class responsible for updating versioned objects in the app by requesting version information from the server.
 * In particular, this includes the list of restaurants and allergens supported by the database.
 * The restaurants list is used for {@link Disambiguation}, {@link MapDisambiguation}, {@link MapSearch}, and auto-complete in the main class, {@link AllergyEX}.
 * The allergens list is used to populate the allergy {@link Preferences} screen.
 * These lists are stored in Android Preferences files whose file names are defined by constants in the AllergyEX activity.
 * 
 * All server results are returned as JSON strings.
 * The Gson library is used to deserialize these strings whenever that operation is required.
 * 
 * @author Ryan Miller
 * 
 * @see com.google.gson.Gson
 *
 */

public class Updater {
	
	private Context context;
	private Service serverService;
	
	private static String GET_VERSIONS_ERROR_MESSAGE = "Failed to acquire version numbers from server. Please make sure you have network connectivity and restart the application.";
	
	/**
	 * Constructor for the class.
	 * Takes the context of the activity it is constructed in in order to make calls to get the Android Preferences files for that context.
	 * Also constructs an instance of {@link Service} to use in performing HTTP requests to the server.
	 * @param theContext the context of the Activity the Updater is (possibly indirectly) constructed in.
	 */
	
	public Updater(Context theContext){
		context = theContext;
		serverService = new Service(context);
	}
	
	/**
	 * 
	 * Requests the version numbers for all versioned objects from the server and calls {@link update} on any versions that have changed.
	 * Version numbers are stored in an Android Preferences file whose name is defined as a constant in {@link AllergyEX} 
	 * The keys for the preferences hash map are defined by the name of the versioned object as stored on the server.
	 * This is the only public method exposed from this class.
	 * 
	 * @return a {@link VersionResult} object that describes what versioned objects changed and any errors that occurred.
	 */
	
	public VersionResult checkForUpdates(){
		boolean isFreshInstall = ObjManager.isFreshInstall(context);
		if (!serverService.getError().equals("")){
			return new VersionResult(false, false, true, serverService.getError());
		}
		String versionNumbersJson;//expects something like
		//[{"name":"allergens","value":"8"},{"name":"restaurants","value":"17"}]
		try {
			versionNumbersJson = serverService.getVersions();
		} catch (Exception e) {
			return new VersionResult(false, false, true, GET_VERSIONS_ERROR_MESSAGE);
		}
		VersionNumber[] versionNumbersFromServer = new Gson().fromJson(versionNumbersJson, VersionNumber[].class);
		SharedPreferences versionNumbersPrefsFile = context.getSharedPreferences(AllergyEX.VERSION_NUMBERS_PREFS, 0);
		SharedPreferences.Editor editor = versionNumbersPrefsFile.edit();
		String storedVersionNumber;
		VersionResult result = new VersionResult();
		for(VersionNumber v : versionNumbersFromServer){
			storedVersionNumber = versionNumbersPrefsFile.getString(v.getName(), "-1");
			if(!storedVersionNumber.equals(v.getVersion())){//if stored version number and version number from server disagree, update the versioned object
				if (v.getName().equals(AllergyEX.RESTAURANT_LIST_KEY)){
					result.setRestaurantsChanged(true);
				}
				if (v.getName().equals(AllergyEX.ALLERGEN_LIST_KEY)){
					result.setAllergensChanged(true);
					
					//if this is not a fresh install, set the flag that there is an unacknowledged update
					if (!isFreshInstall){
						SharedPreferences versions = context.getSharedPreferences(AllergyEX.VERSION_NUMBERS_PREFS, 0);
						Editor edit = versions.edit();
						edit.putBoolean(AllergyEX.UNACKNOWLEDGED_ALLERGEN_UPDATE, true);
						edit.commit();
					}
				}
				if(update(v)){//if update is successful, change version number
					editor.putString(v.getName(), v.getVersion());
				}else{
				}
			}
		}
		editor.commit();
		result.setError(false);
		return result;
	}
	
	/**
	 * Updates a versioned object defined by the {@link VersionNumber} parameter.
	 * Stores the updated object as a JSON string in the JSON strings Android Preferences file defined by a constant in {@link AllergyEX}
	 * 
	 * @param v a {@link VersionNumber} parameter that defines the name and the version of the object to update
	 * @return a boolean that tells if update succeeds or not
	 */
	private boolean update(VersionNumber v){
		//send v.getName() to server as argument and receive the following Json String
		String versionedObjectJson = null;
		try {
			if(v.getName().equals(AllergyEX.ALLERGEN_LIST_KEY)){
				versionedObjectJson = serverService.getAllergens();
			}else if(v.getName().equals(AllergyEX.RESTAURANT_LIST_KEY)){
				versionedObjectJson = serverService.getRestaurants();
			}else{
				return false;
			}
		} catch (Exception e){
			return false;
		}
		//store JSON string in JSON strings prefs file
		SharedPreferences jsonStringsFile = context.getSharedPreferences(AllergyEX.JSON_STRINGS_PREFS, 0);
		SharedPreferences.Editor editor = jsonStringsFile.edit();
		editor.putString(v.getName(), versionedObjectJson);
		editor.commit();
		
		//extra update methods associated with an update on a primary Json string
		if(v.getName().equals(AllergyEX.ALLERGEN_LIST_KEY)){
			addToAllergyPrefs();
		}else if(v.getName().equals(AllergyEX.RESTAURANT_LIST_KEY)){
			saveNewDictionary();
		}
		
		return true;
	}

	/**
	 * Adds any new allergens to the Android Preferences file that tracks whether or not the user is allergic to each allergen.
	 * Called as an extra update method from {@link update}.
	 * The name of the JSON strings Android Preferences file the list of allergens is parsed from and the name of the allergy Android Preferences file the new allergens go into are defined as constant in {@link AllergyEX}.
	 */
	
	private void addToAllergyPrefs(){
		
			String allergenListJson = context.getSharedPreferences(AllergyEX.JSON_STRINGS_PREFS, 0).getString(AllergyEX.ALLERGEN_LIST_KEY, null);
			
			Allergen[] preferenceNames = new Gson().fromJson(allergenListJson, Allergen[].class);
			SharedPreferences allergyPreferences = context.getSharedPreferences(AllergyEX.ALLERGY_PREFS, 0);
			SharedPreferences.Editor editor = allergyPreferences.edit();
			for (Allergen a : preferenceNames) {
				if (!allergyPreferences.contains(a.getId())) {
					editor.putBoolean(a.getId(), false);
				}
			}
			boolean success = editor.commit();
			if (!success) {
			}
	}
	
	/**
	 * Saves the names of all restaurants in a formatted dictionary file used by the restaurant search spell-checker in {@link AllergyEX}.
	 * The name of the dictionary file is defined as a constant in the AllergyEX activity.
	 * The format of the dictionary has restaurant names delimited by newlines.
	 */
	
	private void saveNewDictionary(){
		String restaurantListJson = context.getSharedPreferences(AllergyEX.JSON_STRINGS_PREFS, 0).getString(AllergyEX.RESTAURANT_LIST_KEY, null);
		Restaurant[] restaurants = new Gson().fromJson(restaurantListJson, Restaurant[].class);
		try {
			context.deleteFile(AllergyEX.RESTAURANT_DICTIONARY);//delete file before making new one
			FileOutputStream fos = context.openFileOutput(AllergyEX.RESTAURANT_DICTIONARY, Context.MODE_PRIVATE);
			ArrayList<String> dictionaryEntryList = new ArrayList<String>();
			for(Restaurant r : restaurants){
				if(!dictionaryEntryList.contains(r.getName())){//prevents storage of duplicate entries
					String dictionaryEntry = r.getName()+"\n";
					fos.write(dictionaryEntry.getBytes());
					dictionaryEntryList.add(r.getName());
				}
			}
			fos.close();
		} catch (FileNotFoundException e) {
			//openFileOutput should create the file if it doesn't exist, so this code should be unreachable
		} catch (IOException e) {
		}
	}
}
